package tools

import (
	"bytes"
	"golang.org/x/net/icmp"
	"net"
	"os/exec"
	"runtime"
	"strings"
	"sync"
	"time"
)

type Icmper struct {
	TargetHosts []string
	AliveHosts  []string
	ExistHosts  map[string]struct{}
	Livewg      sync.WaitGroup
	IcmpClient  *icmp.PacketConn
	IcmpFlag    bool
}

func NewIcmper(hosts []string) (icmper Icmper) {
	var err error
	icmper.TargetHosts = hosts
	icmper.ExistHosts = make(map[string]struct{})
	icmper.IcmpClient, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
	if err == nil {
		icmper.IcmpFlag = true
	} else {
		icmper.IcmpFlag = false
	}

	return icmper
}

// 探活IP
func (ipr *Icmper) Run() []string {
	chanHosts := make(chan string, len(ipr.TargetHosts))
	// 消费者
	go func() {
		for ip := range chanHosts {
			if _, ok := ipr.ExistHosts[ip]; !ok && IsContain(ipr.TargetHosts, ip) {
				ipr.ExistHosts[ip] = struct{}{}
				ipr.AliveHosts = append(ipr.AliveHosts, ip)
			}
			ipr.Livewg.Done()
		}
	}()
	// 生产者
	if ipr.IcmpFlag {
		// 尝试有监听icmp探测
		ipr.RunIcmp1(chanHosts)
		defer func() {
			if ipr.IcmpClient != nil {
				ipr.IcmpClient.Close()
			}
		}()
	} else {
		// 优先尝试无监听icmp监测
		conn, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 1*time.Second)
		defer func() {
			if conn != nil {
				conn.Close()
			}
		}()
		if err == nil {
			ipr.RunIcmp2(chanHosts)
		} else {
			ipr.RunPing(chanHosts)
		}
	}

	ipr.Livewg.Wait()
	close(chanHosts)

	return ipr.AliveHosts
}

func execCommandPing(ip string) bool {
	var command *exec.Cmd
	switch runtime.GOOS {
	case "windows":
		command = exec.Command("cmd", "/c", "ping -n 1 -w 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
	case "darwin":
		command = exec.Command("/bin/bash", "-c", "ping -c 1 -W 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
	default: //linux
		command = exec.Command("/bin/bash", "-c", "ping -c 1 -w 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
	}
	outinfo := bytes.Buffer{}
	command.Stdout = &outinfo
	err := command.Start()
	if err != nil {
		return false
	}
	if err = command.Wait(); err != nil {
		return false
	} else {
		if strings.Contains(outinfo.String(), "true") && strings.Count(outinfo.String(), ip) > 2 {
			return true
		} else {
			return false
		}
	}
}
func (ipr *Icmper) RunPing(chanHosts chan string) {
	var wg sync.WaitGroup
	limiter := make(chan struct{}, 50)
	for _, host := range ipr.TargetHosts {
		wg.Add(1)
		limiter <- struct{}{}
		go func(host string) {
			if execCommandPing(host) {
				ipr.Livewg.Add(1)
				chanHosts <- host
			}
			<-limiter
			wg.Done()
		}(host)
	}
	wg.Wait()
}

// 有监听icmp监测
func (ipr *Icmper) RunIcmp1(chanHosts chan string) {
	var endflag bool = false
	go func() {
		for {
			if endflag {
				return
			}
			msg := make([]byte, 100)
			_, sourceIP, _ := ipr.IcmpClient.ReadFrom(msg)
			if sourceIP != nil {
				ipr.Livewg.Add(1)
				chanHosts <- sourceIP.String()
			}
		}
	}()

	// 向目标ip发送报文
	for _, host := range ipr.TargetHosts {
		dst, _ := net.ResolveIPAddr("ip", host)
		IcmpByte := makemsg(host)
		ipr.IcmpClient.WriteTo(IcmpByte, dst)
	}

	// 根据 hosts 数目修改 icmp监听时间
	var start time.Time = time.Now()
	for {
		if len(ipr.AliveHosts) == len(ipr.TargetHosts) {
			break
		}
		since := time.Since(start)
		var wait time.Duration
		switch {
		case len(ipr.TargetHosts) <= 256:
			wait = time.Second * 3
		default:
			wait = time.Second * 6
		}
		if since > wait {
			break
		}
	}
	endflag = true
	ipr.IcmpClient.Close()
}

// 无监听icmp监测
func (ipr *Icmper) RunIcmp2(chanHosts chan string) {
	num := 1000
	if len(ipr.TargetHosts) < num {
		num = len(ipr.TargetHosts)
	}
	var wg sync.WaitGroup
	limiter := make(chan struct{}, num)
	for _, host := range ipr.TargetHosts {
		wg.Add(1)
		limiter <- struct{}{}
		go func(host string) {
			if icmpalive(host) {
				ipr.Livewg.Add(1)
				chanHosts <- host
			}
			<-limiter
			wg.Done()
		}(host)
	}
	wg.Wait()
	close(limiter)
}

func makemsg(host string) []byte {
	msg := make([]byte, 40)
	id0, id1 := genIdentifier(host)
	msg[0] = 8
	msg[1] = 0
	msg[2] = 0
	msg[3] = 0
	msg[4], msg[5] = id0, id1
	msg[6], msg[7] = genSequence(1)
	check := checkSum(msg[0:40])
	msg[2] = byte(check >> 8)
	msg[3] = byte(check & 255)
	return msg
}

func checkSum(msg []byte) uint16 {
	sum := 0
	length := len(msg)
	for i := 0; i < length-1; i += 2 {
		sum += int(msg[i])*256 + int(msg[i+1])
	}
	if length%2 == 1 {
		sum += int(msg[length-1]) * 256
	}
	sum = (sum >> 16) + (sum & 0xffff)
	sum = sum + (sum >> 16)
	answer := uint16(^sum)
	return answer
}

func genSequence(v int16) (byte, byte) {
	ret1 := byte(v >> 8)
	ret2 := byte(v & 255)
	return ret1, ret2
}

func genIdentifier(host string) (byte, byte) {
	return host[0], host[1]
}

func icmpalive(host string) bool {
	startTime := time.Now()
	conn, err := net.DialTimeout("ip4:icmp", host, 2*time.Second)
	if err != nil {
		return false
	}
	defer conn.Close()
	if err := conn.SetDeadline(startTime.Add(2 * time.Second)); err != nil {
		return false
	}
	msg := makemsg(host)
	if _, err := conn.Write(msg); err != nil {
		return false
	}

	receive := make([]byte, 60)
	if _, err := conn.Read(receive); err != nil {
		return false
	}

	return true
}
